package "hamelin"

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"
	"time"

	"gopkg.in/ini.v1"
)

var (
	DefaultDBTree = LoadTree(DefaultIniPath())
)

type TreeMap struct {
	Root string
	Path string
	Name string
	pre  *TreeMap
	subs map[string]*TreeMap
}

func DefaultIniPath() string {
	rootconfig, _ := os.UserHomeDir()
	rootconfig = path.Join(rootconfig, "TreeDb.ini")
	return rootconfig
}

func NewTree(rootPath string) (tree *TreeMap, err error) {
	tree = new(TreeMap)
	if Exists(rootPath) {
		tree.Path = rootPath
		tree.Name = "Root"
		tree.Root = rootPath
		if f := path.Join(tree.Path, "tree.ini"); !Exists(f) {
			ioutil.WriteFile(f, []byte(fmt.Sprintf(`[base]
name=Root
root = %s
		`, tree.Root)), os.ModePerm)
		}

		tree.LoadMap()
	} else {
		err = errors.New("no such root dir:" + rootPath)
	}
	return
}

// func (tree *TreeMap) RefreshTree(root string) {
// 	if tree.Path != root && Exists(root) {
// 		tree.Path = root
// 	}
// 	files, err := ioutil.ReadDir(tree.Path)
// 	if err != nil {
// 		log.Fatal(err)
// 	}
// 	if tree.subs == nil {
// 		tree.subs = make(map[string]*TreeMap)
// 	}
// 	for _, file := range files {
// }

func LoadTree(dbIni string) (tree TreeMap) {
	cfg, err := ini.Load(dbIni)
	if err != nil {
		log.Fatal("Root", err)
	}

	for _, k := range cfg.Section("base").KeyStrings() {
		if k == "root" {
			tree.Root = cfg.Section("base").Key(k).String()
			continue
		}
	}
	if !Exists(tree.Root) {
		os.Mkdir(tree.Root, os.ModePerm)
		ioutil.WriteFile(path.Join(tree.Root, "tree.ini"), []byte(fmt.Sprintf(`[base]
name=Root
root = %s
		`, tree.Root)), os.ModePerm)
	}
	tree.Path = tree.Root
	tree.LoadMap()

	return
}

func (tree *TreeMap) LoadIni(dirpath string) (sub *TreeMap) {
	sub = new(TreeMap)
	sub.Path = dirpath
	cfg, err := ini.Load(path.Join(dirpath, "tree.ini"))
	if err != nil {
		log.Fatal(err)
	}
	if path.Base(dirpath) != sub.Name {
		cfg.Section("base").Key("name").SetValue(path.Base(dirpath))
		cfg.SaveTo(path.Join(dirpath, "tree.ini"))
	}
	sub.Name = path.Base(dirpath)
	sub.Root = cfg.Section("base").Key("root").String()
	sub.pre = tree
	return
}

func Exists(file string) bool {
	if _, err := os.Stat(file); err == nil {
		return true
	}
	return false
}

// LoadMap
func (tree *TreeMap) LoadMap() {

	if info, err := os.Stat(tree.Path); err == nil {
		if info.IsDir() && Exists(path.Join(tree.Path, "tree.ini")) {
			files, err := ioutil.ReadDir(tree.Path)
			if err != nil {
				log.Fatal(err)
			}
			if tree.subs == nil {
				tree.subs = make(map[string]*TreeMap)
			}
			for _, file := range files {
				subpath := path.Join(tree.Path, file.Name())
				if file.IsDir() && Exists(path.Join(subpath, "tree.ini")) {
					sub := tree.LoadIni(subpath)
					tree.subs[sub.Name] = sub
					sub.pre = tree
					sub.LoadMap()
				}
			}
		}
	}
}

func (tree *TreeMap) Ls() {
	if tree.subs == nil {
		tree.subs = make(map[string]*TreeMap)
		files, err := ioutil.ReadDir(tree.Path)
		if err != nil {
			log.Fatal(err)
		}
		if tree.subs == nil {
			tree.subs = make(map[string]*TreeMap)
		}
		for _, file := range files {
			subpath := path.Join(tree.Path, file.Name())
			if file.IsDir() && Exists(path.Join(subpath, "tree.ini")) {
				sub := tree.LoadIni(subpath)
				tree.subs[sub.Name] = sub
			}
		}
	}
}

func (tree *TreeMap) SearchIndex(key string) *Node {
	cfg := tree.GetInfo()
	for _, sec := range cfg.SectionStrings() {
		if sec == "base" {
			continue
		}
		for _, indexStr := range cfg.Section(sec).KeyStrings() {
			if strings.Contains(indexStr, key) {
				return tree.GetNode(sec)
			}
		}
	}
	return nil
}

func (tree *TreeMap) GetNode(nodeName string) (node *Node) {
	node = &Node{
		Name: nodeName,
		Tree: tree,
	}
	return
}

func (tree *TreeMap) LsNode() (ns []*Node) {
	files, err := ioutil.ReadDir(tree.Path)
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		if !file.IsDir() && strings.HasSuffix(file.Name(), ".node") {
			node := &Node{
				Name: strings.SplitN(file.Name(), ".node", 2)[0],
				Tree: tree,
			}
			ns = append(ns, node)
		}
	}
	return
}

func (tree *TreeMap) Search(key string, level int) (node *Node) {
	tree.Ls()
	node = tree.SearchIndex(key)
	if node != nil {
		return
	} else {
		if level > 0 {
			for _, n := range tree.LsNode() {
				// fmt.Println(vt.Name)
				if _, ok := n.Find(key); ok {
					return n
				}
			}
		}

		for _, subtree := range tree.subs {
			return subtree.Search(key, level)
		}
		return nil
	}
}

func (tree *TreeMap) Location(key string) (sub *TreeMap) {
	tree.Ls()
	if strings.Contains(tree.Name, key) {
		return tree
	} else {
		for _, vt := range tree.subs {
			// fmt.Println(vt.Name)
			return vt.Location(key)
		}
		return nil
	}
}
func (tree *TreeMap) AddTree(name string) (subtree *TreeMap) {
	if !Exists(path.Join(tree.Path, name)) {
		subtree = new(TreeMap)
		subtree.Name = name
		subtree.Path = path.Join(tree.Path, name)
		subtree.Root = tree.Root
		if err := os.Mkdir(subtree.Path, os.ModePerm); err != nil {
			log.Fatal("sub tree:", err)
		}
		cfg := ini.Empty()
		cfg.Section("base").Key("name").SetValue(name)
		cfg.Section("base").Key("root").SetValue(tree.Root)
		cfg.SaveTo(path.Join(subtree.Path, "tree.ini"))
		if tree.subs == nil {
			tree.subs = make(map[string]*TreeMap)
		}
		subtree.pre = tree
		tree.subs[name] = subtree

	} else {
		subtree = tree.Location(name)
	}
	return
}

func (tree *TreeMap) Delete() (pre *TreeMap) {
	if tree.pre == nil {
		log.Println("must have a pre tree !!!")
		return tree
	}
	delete(tree.pre.subs, tree.Name)
	for n, v := range tree.subs {
		if _, ok := tree.pre.subs[n]; !ok {
			tree.pre.subs[n] = v
		}
	}
	if err := os.RemoveAll(tree.Path); err != nil {
		log.Fatal(err)
	}
	tree.pre.LoadMap()
	pre = tree.pre

	return
}

func (tree *TreeMap) GetInfo() (cfg *ini.File) {
	cfg, err := ini.Load(path.Join(tree.Path, "tree.ini"))
	if err != nil {
		log.Fatal("Get Header: "+tree.Path, err)
	}
	return
}

func (tree *TreeMap) AddNode(name string, content interface{}) {
	// Save : save json/ini/ other

	node := &Node{
		Name: name,
		Tree: tree,
	}
	if Exists(node.Path()) {
		return
	}
	xType := fmt.Sprintf("%T", content)
	switch xType {
	case "string":
		ioutil.WriteFile(node.Path(), []byte(content.(string)), os.ModePerm)
	case "[]uint8":
		ioutil.WriteFile(node.Path(), content.([]byte), os.ModePerm)
	case "[]byte":
		ioutil.WriteFile(node.Path(), content.([]byte), os.ModePerm)
	}

}

func (tree *TreeMap) Nodes(filter ...string) (vs []*Node) {
	files, err := ioutil.ReadDir(tree.Path)
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		if !file.IsDir() && strings.HasSuffix(file.Name(), ".node") {
			v := Node{
				Name: strings.Split(file.Name(), ".node")[0],
				Tree: tree,
			}
			if len(filter) > 0 {
				if !strings.Contains(file.Name(), filter[0]) {
					continue
				}
			}

			vs = append(vs, &v)

		}
	}
	return
}

// Proc :  Base Proc interface
type Proc interface {
	Log()
	Save(proc ...string)
	Find(key string) (Value, bool)
}

type Value interface {
	Val() string
}

func Valued(args interface{}) Val {
	xType := fmt.Sprintf("%T", args)
	return Val{
		tp:   xType,
		data: args,
	}
}

type Val struct {
	data interface{}
	tp   string
}

func (val Val) Val() string {
	switch val.tp {
	case "int":

		return fmt.Sprintf("%d", val.data)
	case "str":
		return val.data.(string)
	case "time":
		return val.data.(time.Time).Format("2006-01-02 15:04:05")
	default:
		return fmt.Sprintf("%v", val.data)
	}
}

// Node redefine
type Node struct {
	Name string
	Tree *TreeMap
}

// Find by key contains in key
func (node *Node) Find(key string) (interface{}, bool) {
	d := node.Data()
	if strings.Contains(d, key) {
		return d, true
	}
	return "", false
}

func (node *Node) Path() string {
	return path.Join(node.Tree.Path, node.Name+".node")
}
func (node *Node) Data() (data string) {
	buf, err := ioutil.ReadFile(node.Path())
	if err != nil {
		log.Fatal(err)
	}
	data = string(buf)
	return
}

func (node *Node) CreateIndex(keys ...string) {
	cfg := node.Tree.GetInfo()
	file, err := os.Open(node.Path())
	if err != nil {
		log.Fatal("Create index :", err)
	}
	defer file.Close()
	fileScanner := bufio.NewScanner(file)
	kamp := make(map[string]int)
	for fileScanner.Scan() {
		for _, k := range keys {
			if strings.Contains(fileScanner.Text(), k) {
				kamp[k]++
			}
		}
	}
	for k, v := range kamp {
		cfg.Section(node.Name).Key(k).SetValue(fmt.Sprintf("%d", v))
	}
	// cfg.Section(node.Name).Key(index)
}

func Test() {
	fmt.Println("hello")
	tt, err := NewTree("/tmp/He")
	if err != nil {
		log.Fatal(err)
	}
	t2 := tt.AddTree("test2")
	tt.AddTree("test44")
	t2.AddNode("one", `Hello wrold? this \nasf s`)
	t3 := t2.AddTree("Deep")
	t3.AddTree("some")

	chi := tt.Location("so")
	if chi != nil {
		fmt.Println(chi.Name)
	}

}
